package com.shaw.mybatisx.proxy;

import com.shaw.mybatisx.annotation.Interceptors;
import com.shaw.mybatisx.annotation.Signature;
import com.shaw.mybatisx.plugin.Interceptor;
import com.shaw.mybatisx.plugin.Invocation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Created by asus on 2018/4/10.
 */
public class PluginProxy implements InvocationHandler {
    private Interceptor interceptor;

    private Object target;

    private Map<Class<?>,Set<Method>> signatureMap;

    private PluginProxy(Object target,Interceptor interceptor,Map<Class<?>,Set<Method>> signatureMap){
        this.interceptor = interceptor;
        this.target = target;
        this.signatureMap = signatureMap;
    }

    public static Object warp(Object target,Interceptor interceptor){
        Map<Class<?>,Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?>[] interfaces = getInterfaces(target.getClass(),signatureMap);
        if(interfaces.length>0) {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), interfaces, new PluginProxy(target, interceptor, signatureMap));
        }
        return target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Set<Method> methods = this.signatureMap.get(method.getDeclaringClass());
        if(methods!=null&&methods.contains(method)){
            return interceptor.intercept(new Invocation(target,method,args));
        }
        return method.invoke(target,args);
    }

    private static Map<Class<?>,Set<Method>> getSignatureMap(Interceptor interceptor) {
        Map<Class<?>,Set<Method>> map = new HashMap<>();
        Interceptors interceptorAnnotations = interceptor.getClass().getAnnotation(Interceptors.class);
        Signature[] signatureAnnotations = interceptorAnnotations.value();
        for(Signature signatureAnnotation:signatureAnnotations){
            Set<Method> methods = map.get(signatureAnnotation.type());
            if(methods==null){
                methods = new HashSet<>();
                map.put(signatureAnnotation.type(),methods);
            }
            try {
                Method method = signatureAnnotation.type().getMethod(signatureAnnotation.method(),signatureAnnotation.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }

        }
        return map;
    }

    private static Class<?>[] getInterfaces(Class clazz,Map<Class<?>,Set<Method>> signatureMap){
        Set<Class<?>> interfaceSet = new HashSet<>();
        Class<?>[] interfaces = clazz.getInterfaces();
        for(Class<?> interfaceClass:interfaces){
            if(signatureMap.containsKey(interfaceClass)){
                interfaceSet.add(interfaceClass);
            }
        }
        return interfaceSet.toArray(new Class<?>[interfaceSet.size()]);
    }
}
